package com.xiashitech.agent.instrumentation.httprequest;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONUtil;
import com.xiashitech.agent.cache.system.CacheEnv;
import com.xiashitech.agent.config.StartConfig;
import com.xiashitech.agent.dto.UserRequest;
import com.xiashitech.agent.instrumentation.base.AbstractAttributesExtractor;
import com.xiashitech.agent.instrumentation.base.context.TraceContextVars;
import com.xiashitech.agent.instrumentation.base.context.bridge.BridgeBaggageBuilder;
import com.xiashitech.agent.instrumentation.context.SpringApplicationContext;
import com.xiashitech.agent.instrumentation.utils.BaggageUtil;
import com.xiashitech.agent.instrumentation.utils.MachineAttributesUtil;
import com.xiashitech.agent.utils.*;
import com.xiashitech.interfaces.agent.dto.AgentConfigDTO;
import com.xiashitech.interfaces.agent.dto.SystemInfoDTO;
import io.opentelemetry.javaagent.shaded.io.opentelemetry.api.baggage.Baggage;
import io.opentelemetry.javaagent.shaded.io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.javaagent.shaded.io.opentelemetry.api.trace.Span;
import io.opentelemetry.javaagent.shaded.io.opentelemetry.context.Context;
import javassist.bytecode.stackmap.BasicBlock;
import org.apache.catalina.connector.RequestFacade;
import org.jetbrains.annotations.Nullable;

import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.*;


public class XSHttpRequestAttributesExtractor extends AbstractAttributesExtractor<UserRequest, ServletResponse> {
    public String getAttributesExtractorName() {
        return XSHttpRequestAttributesExtractor.class.getName();
    }

    @Override
    public void onXSStart(AttributesBuilder attributesBuilder, Context context, UserRequest userRequest) {
        DurationPrinter durationPrinter = new DurationPrinter("XSHttpRequestAttributesExtractor");
        System.out.println("###http filter onXSStart TraceId "+ Span.current().getSpanContext().getTraceId());
        Baggage baggage = SessionUtil.syncBaggage();
        durationPrinter.printElapseDuration("onXSStart syncBaggage");
        baggage = BaggageUtil.mergeBaggageFromContext(baggage, context).build();
        durationPrinter.printElapseDuration("onXSStart mergeBaggageFromContext");
        // System.out.println("###start001 baggage " + JsonUtil.convertObjectToString(baggage));
        // 以下引起 converObjectToString 异常：It is illegal to call this method if the current request is not in asynchronous mode
        // System.out.println("###start001 userRequest " + JsonUtil.convertObjectToString(userRequest));
        //ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = (HttpServletRequest) userRequest.getRequest();
        String host = request.getHeader("host");
        HttpSession session = request.getSession(false); // 不能设置为 false，会报 NullPointerException
        if(session!=null) {


            Enumeration<String> attrs = session.getAttributeNames();
            System.out.println("###httpFilterSession sessionId " + request.getSession().getId());
            // 遍历attrs中的
            while (attrs.hasMoreElements()) {
                // 获取session键值
                String name = attrs.nextElement().toString();
                // 根据键值取session中的值
                Object value = session.getAttribute(name);

                // 打印结果
                try {


                    System.out.println("###httpFilterSession------sessionName:" + name + ";sessionValue:" + JsonUtil.convertObjectToString(value) + "--------\n");

                 /*   if(ObjectFieldUtil.isBasicType(vakue))
                    {
                        System.out.println("###httpFilterSession------sessionName:" + name + ";sessionValue:" + vakue + "--------\n");
                    }
                    else
                    {
                        System.out.println("###httpFilterSession------sessionName:" + name + ";sessionValue:" + ObjectFieldUtil.printObjectFields(vakue,"######httpFilterSession") + "--------\n");

                    }*/
                }
                catch (Throwable e)
                {
                    StartConfig.throwableStackPrinter(e);
                }

            }
        }
        else {
            System.out.println("###httpFilterSession get session null");

        }
        // System.out.println("###session:" + session.toString());
        // System.out.println("###url request Serverlet Fileter：host:+" + host + ";url:" + request.getRequestURI() + "?" + request.getQueryString());
        // System.out.println("###CacheEnv host:" + CacheEnv.getHost());
        durationPrinter.printElapseDuration("onXSStart requestread");
        if (CacheEnv.getHost() == null) {
            CacheUtil.syncAgentHost(request.getHeader("host"));
        }
        durationPrinter.printElapseDuration("onXSStart syncAgentHost");
        try {
            if ((userRequest.getUserid() == null || "".equals(userRequest.getUserid())) && baggage.size() > 0) {
                userRequest.setUserid(baggage.getEntryValue("xs.userId"));
                userRequest.setRequestDate(baggage.getEntryValue("xs.requestDate"));
                userRequest.setSessionId(baggage.getEntryValue("xs.sessionId"));
                userRequest.setSessionUpdate(baggage.getEntryValue("xs.sessionUpdate") == null ? false : Boolean.valueOf(baggage.getEntryValue("xs.sessionUpdate")));
            }
            setLoginRequest(userRequest);
            durationPrinter.printElapseDuration("onXSStart setLoginRequest");
            String clientIp = detectClientIp((HttpServletRequest) userRequest.getRequest());
            attributesBuilder.put("http.client_ip", clientIp == null ? "" : clientIp);
            durationPrinter.printElapseDuration("onXSStart clientIp");
        } catch (Throwable e) {
            StartConfig.throwableStackPrinter(e);
        }
    }

    @Override
    public void onXSEnd(AttributesBuilder attributesBuilder, Context context, UserRequest userRequest, ServletResponse response, @Nullable Throwable throwable) {
        DurationPrinter durationPrinter = new DurationPrinter("XSHttpRequestAttributesExtractor");
        if (userRequest != null) {
            try {
                Baggage baggage = SessionUtil.syncBaggage();
                baggage = BaggageUtil.mergeBaggageFromContext(baggage,context).build();
                attributesBuilder.put("xs.userId", userRequest.getUserid() == null ? "" : userRequest.getUserid());
                attributesBuilder.put("xs.loginName", userRequest.getLoginName() == null ? "" : userRequest.getLoginName());
                attributesBuilder.put("xs.sessionId", userRequest.getSessionId() == null ? "" : userRequest.getSessionId());
                attributesBuilder.put("xs.requestDate", userRequest.getRequestDate() == null ? "" : userRequest.getRequestDate().toString());
                attributesBuilder.put("xs.sessionUpdate", userRequest.getSessionUpdate() == null ? false : userRequest.getSessionUpdate());
                attributesBuilder.put("xs.loginRequest", userRequest.getLoginRequest() == null ? false : userRequest.getLoginRequest());
                attributesBuilder.put("xs.systemId", StartConfig.getParamValueFromKey(StartConfig.systemId));
                attributesBuilder.put("xs.systemReg", SystemInfoDTO.getConfig().getSystemExist());
                attributesBuilder.put("xs.sourceSystemId", baggage.getEntryValue("xsAuthSystemId") == null ? StartConfig.getParamValueFromKey(StartConfig.systemId) : baggage.getEntryValue("xsAuthSystemId"));
                TraceContextVars contextVars = TraceContextVars.get();
                baggage.forEach((key,value) -> contextVars.set(key,value.getValue()));
                contextVars.set("xs.sessionUpdate", userRequest.getSessionUpdate() == null ? "false" : userRequest.getSessionUpdate().toString());
            } catch (Throwable e) {
                StartConfig.throwableStackPrinter(e);
            }
        }
        durationPrinter.printElapseDuration("onXSEnd userRequest");
        if (response != null) {
            System.out.println("###http apiRecordSize  tradeId " + Span.current().getSpanContext().getTraceId());
            if (response instanceof XSHttpServletResponseWrapper) {
                //   JsonUtil.getRecordSize(response,20,1,0);
                XSHttpServletResponseWrapper xsHttpServletResponseWrapper = (XSHttpServletResponseWrapper) response;
                int apiRecordSize = 0;
                try {
                    String result = new String(xsHttpServletResponseWrapper.getByteArray(), "utf-8");
                    durationPrinter.printElapseDuration("onXSEnd WrapperToString");
                    AgentConfigDTO agentConfig = AgentConfigDTO.getConfig();
                    String systemId = StartConfig.getParamValueFromKey(StartConfig.systemId);
                    Map<String, Boolean> apiRecordSizeRuleMap = agentConfig.getApiRecordSizeRule();
                    durationPrinter.printElapseDuration("onXSEnd WTSB");
                    if (Optional.ofNullable(apiRecordSizeRuleMap).isPresent() && apiRecordSizeRuleMap.get(systemId) != null && apiRecordSizeRuleMap.get(systemId) == true) {
                        String requestURI = ((RequestFacade)userRequest.getRequest()).getRequestURI();
                        String[] urlStr = requestURI.split("/");
                        String apiMetaServiceMethod = "_"+ urlStr[urlStr.length-2]+"_"+urlStr[urlStr.length-1];
                        //String fullyQualifiedName = "ApiService."+systemId+"."+apiMetaServiceMethod;
                        String  fullyQualifiedName = requestURI.replace("/","_");
                        //fullyQualifiedName = "ApiService.yongdavip._getUserTokenDemo81";
                        //String metaUrl = StrUtil.format(StartConfig.getParamValueFromKey(StartConfig.agentService)+StartConfig.serverContextPath + "/v1/agent/getApiByFQN?fqn={}",fullyQualifiedName);
                        String metaUrl = StrUtil.format(StartConfig.getAgentServiceRoot() + "/v1/agent/getApiByFQN?fqn={}",fullyQualifiedName);
                        String resultJson =  HttpRequest.get(metaUrl).execute().body();
                        String apiRecordSizeRule = JSONUtil.parseObj(resultJson).getStr("apiRecordSizeRule");
                        String[] rules = apiRecordSizeRule.split("_");
                        int deep = Integer.parseInt(rules[0]);
                        int index = Integer.parseInt(rules[1]);
                        apiRecordSize = JsonUtil.getRecordSize(result,deep,1,index);
                        durationPrinter.printElapseDuration("onXSEnd notnull");

                    } else {
                        apiRecordSize = JsonUtil.getRecordSize(result, 20, 1, 0);
                        durationPrinter.printElapseDuration("onXSEnd apinullA");
                    }
                    TraceContextVars.get()
                            .set("xs.api.records.size", String.valueOf(apiRecordSize));
                    attributesBuilder.put("xs.api.records.size", apiRecordSize);
                    System.out.println("###http apiRecordSize " + apiRecordSize);
                    int byteArraySize = xsHttpServletResponseWrapper.getByteArray().length;
                    int charArraySize = xsHttpServletResponseWrapper.getCharArray().length;
                    durationPrinter.printElapseDuration("onXSEnd getCharArray");
                    if (byteArraySize != 0) {
                        attributesBuilder.put("xs.responseSize", byteArraySize);
                    } else if (charArraySize != 0) {
                        attributesBuilder.put("xs.responseSize", charArraySize);
                    } else {
                        attributesBuilder.put("xs.responseSize", -1);
                    }
                    String port = SpringApplicationContext.getPort();
                    if (port != null) {
                        attributesBuilder.put("xs.server.port", port);
                    }
                    durationPrinter.printElapseDuration("onXSEnd setvalue");
                    MachineAttributesUtil.fillMachineAttributes(attributesBuilder);
                    durationPrinter.printElapseDuration("onXSEnd fillMachineAttributes");
                } catch (Throwable e) {
                    StartConfig.throwableStackPrinter(e);
                }

                //  JsonUtil.getRecordSize(result,20,1,0);

            }
        }

    }

    private void setLoginRequest(UserRequest userRequest) {
        String loginPathKey = AgentConfigDTO.getConfig().getLoginPath().get(StartConfig.getParamValueFromKey(StartConfig.systemId));
        if (userRequest.getRequest() instanceof HttpServletRequest) {
            HttpServletRequest httpServletRequest = (HttpServletRequest) userRequest.getRequest();
            if (loginPathKey != null && httpServletRequest.getRequestURI().contains(loginPathKey)) {
                userRequest.setLoginRequest(Boolean.TRUE);
            }
        }
    }

    public String detectClientIp(HttpServletRequest request) {
        try {
            String clientIp = clientIp(request);
            if (clientIp == null || clientIp.isEmpty() || "unknown".equalsIgnoreCase(clientIp)) {
                clientIp = request.getHeader("Proxy-Client-IP");
            }
            if (clientIp == null || clientIp.isEmpty() || "unknown".equalsIgnoreCase(clientIp)) {
                clientIp = request.getHeader("WL-Proxy-Client-IP");
            }
            if (clientIp == null || clientIp.isEmpty() || "unknown".equalsIgnoreCase(clientIp)) {
                clientIp = request.getHeader("HTTP_CLIENT_IP");
            }
            if (clientIp == null || clientIp.isEmpty() || "unknown".equalsIgnoreCase(clientIp)) {
                clientIp = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            if (clientIp == null || clientIp.isEmpty() || "unknown".equalsIgnoreCase(clientIp)) {
                clientIp = request.getHeader("X-Real-IP");
            }
            if (clientIp == null || clientIp.isEmpty() || "unknown".equalsIgnoreCase(clientIp)) {
                clientIp = request.getRemoteAddr();
            }
            return clientIp;
        } catch (Throwable e) {
            StartConfig.throwableStackPrinter(e);
        }
        return null;
    }

    private String clientIp(HttpServletRequest request) {
        String forwarded = firstHeaderValue(getRequestHeaderValues(request, "forwarded"));
        if (forwarded != null) {
            forwarded = extractClientIpFromForwardedHeader(forwarded);
            if (forwarded != null) {
                return forwarded;
            }
        }

        forwarded = firstHeaderValue(getRequestHeaderValues(request, "x-forwarded-for"));
        return forwarded != null ? extractClientIpFromForwardedForHeader(forwarded) : null;
    }

    private List<String> getRequestHeaderValues(HttpServletRequest request, String name) {
        Enumeration<String> values = request.getHeaders(name);
        return values == null ? Collections.emptyList() : Collections.list(values);
    }

    private String firstHeaderValue(List<String> values) {
        return values.isEmpty() ? null : values.get(0);
    }

    private String extractClientIpFromForwardedHeader(String forwarded) {
        int start = forwarded.toLowerCase().indexOf("for=");
        if (start < 0) {
            return null;
        }
        start += 4; // start is now the index after for=
        if (start >= forwarded.length() - 1) { // the value after for= must not be empty
            return null;
        }
        return extractIpAddress(forwarded, start);
    }

    private String extractClientIpFromForwardedForHeader(String forwardedFor) {
        return extractIpAddress(forwardedFor, 0);
    }

    private static String extractIpAddress(String forwarded, int start) {
        if (forwarded.length() == start) {
            return null;
        }
        if (forwarded.charAt(start) == '"') {
            return extractIpAddress(forwarded, start + 1);
        }
        if (forwarded.charAt(start) == '[') {
            int end = forwarded.indexOf(']', start + 1);
            if (end == -1) {
                return null;
            }
            return forwarded.substring(start + 1, end);
        }
        boolean inIpv4 = false;
        for (int i = start; i < forwarded.length(); i++) {
            char c = forwarded.charAt(i);
            if (c == '.') {
                inIpv4 = true;
            } else if (c == ',' || c == ';' || c == '"' || (inIpv4 && c == ':')) {
                if (i == start) { // empty string
                    return null;
                }
                return forwarded.substring(start, i);
            }
        }
        return forwarded.substring(start);
    }
}
